program SpecialFX;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Classes,
  SampleCommon,
  DIrr in '..\..\DSource\DIrr.pas',
  DIrr.GUI in '..\..\DSource\DIrr.GUI.pas',
  DIrr.Color in '..\..\DSource\DIrr.Color.pas',
  DIrr.Rect in '..\..\DSource\DIrr.Rect.pas',
  DIrr.Event in '..\..\DSource\DIrr.Event.pas',
  DIrr.Vector2D in '..\..\DSource\DIrr.Vector2D.pas',
  DIrr.Vector3D in '..\..\DSource\DIrr.Vector3D.pas',
  DIrr.Video in '..\..\DSource\DIrr.Video.pas',
  DIrr.Base in '..\..\DSource\DIrr.Base.pas',
  DIrr.Types in '..\..\DSource\DIrr.Types.pas',
  DIrr.Scene in '..\..\DSource\DIrr.Scene.pas',
  DIrr.Aabbox3D in '..\..\DSource\DIrr.Aabbox3D.pas',
  DIrr.Matrix4 in '..\..\DSource\DIrr.Matrix4.pas',
  DIrr.Texture in '..\..\DSource\DIrr.Texture.pas',
  DIrr.Consts in '..\..\DSource\DIrr.Consts.pas',
  DIrr.Structs in '..\..\DSource\DIrr.Structs.pas',
  DIrr.Mem in '..\..\DSource\DIrr.Mem.pas',
  DIrr.Collections in '..\..\DSource\DIrr.Collections.pas',
  DIrr.Dimension2D in '..\..\DSource\DIrr.Dimension2D.pas',
  DIrr.Particle in '..\..\DSource\DIrr.Particle.pas';

var
  DriverType: E_DRIVER_TYPE;
  Device: TIrrlichtDevice;
  Driver: TVideoDriver;
  i: Char;
  Shadows: Boolean;
  Smgr: TSceneManager;
  Mesh: TAnimatedMesh;
  TheMesh: TMesh;
  Node: TSceneNode;
  MeshManipulator: TMeshManipulator;
  Texture: TTexture;
  MaterialPtr: PMaterial;
  Anim: TSceneNodeAnimator;
  ps: TParticleSystemSceneNode;
  em: TParticleEmitter;
  eparam: SEmitterParams;
  paf: TParticleAffector;
  n: TVolumeLightSceneNode;
  textures: TInputDataList;
  t: TTexture;
  g: S32;
  glow: TSceneNodeAnimator;
  anode: TAnimatedMeshSceneNode;
  camera: TCameraSceneNode;

  FPS, lastFPS: S32;
  TitleStr, tmp: String;
begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    Writeln('Please press ''y'' if you want to use realtime shadows.');
    Readln(i);

    Shadows := (UpCase(i) = 'Y');

    // ask user for driver
    DriverType := DriverChoiceConsole;
    if DriverType = EDT_COUNT then
    begin
      ExitCode := 1;
      Exit;
    end;

    (*
    Create device and exit if creation failed. We make the stencil flag
    optional to avoid slow screen modes for runs without shadows.
    *)
    Device := createDevice(DriverType, [640, 480], 16, False, Shadows);
    if Device = nil then
    begin
      ExitCode := 1;   // could not create selected driver.
      Exit;
    end;

	  Driver := Device.GetVideoDriver;
    Smgr := Device.GetSceneManager;

    (*
    For our environment, we load a .3ds file. It is a small room I modelled
    with Anim8or and exported into the 3ds format because the Irrlicht
    Engine does not support the .an8 format. I am a very bad 3d graphic
    artist, and so the texture mapping is not very nice in this model.
    Luckily I am a better programmer than artist, and so the Irrlicht
    Engine is able to create a cool texture mapping for me: Just use the
    mesh manipulator and create a planar texture mapping for the mesh. If
    you want to see the mapping I made with Anim8or, uncomment this line. I
    also did not figure out how to set the material right in Anim8or, it
    has a specular light color which I don't really like. I'll switch it
    off too with this code.
    *)

    Mesh := Smgr.GetMesh('..\..\media\room.3ds');

    MeshManipulator := Smgr.GetMeshManipulator;
    TheMesh := Mesh.GetMesh(0);
    MeshManipulator.FlipSurfaces(nil);
    MeshManipulator.MakePlanarTextureMapping(TheMesh, 0.004);

    Node := Smgr.AddAnimatedMeshSceneNode(Mesh);
    Texture := Driver.GetTexture('..\..\media\wall.jpg');
    Node.SetMaterialTexture(0, Texture);
    MaterialPtr := Node.GetMaterial(0);
    MaterialPtr^.FSpecularColor.Color32 := 0;

    (*
    Now, for the first special effect: Animated water. It works like this:
    The WaterSurfaceSceneNode takes a mesh as input and makes it wave like
    a water surface. And if we let this scene node use a nice material like
    the EMT_REFLECTION_2_LAYER, it looks really cool. We are doing this
    with the next few lines of code. As input mesh, we create a hill plane
    mesh, without hills. But any other mesh could be used for this, you
    could even use the room.3ds (which would look really strange) if you
    want to.
    *)

    Mesh := Smgr.AddHillPlaneMesh
            (
              'myHill',
              Dimension2DF(20, 20),
              Dimension2DU(40, 40),
              nil,
              0,
              Dimension2DF(0, 0),
              Dimension2DF(10, 10)
            );
    Node := Smgr.AddWaterSurfaceSceneNode(Mesh.GetMesh(0), 3.0, 300.0, 30.0);
    Node.SetPosition(Vector3DF(0, 7, 0));

    Node.SetMaterialTexture(0, Driver.GetTexture('..\..\media\stones.jpg'));
    Node.SetMaterialTexture(1, Driver.GetTexture('..\..\media\water.jpg'));

    Node.SetMaterialType(EMT_REFLECTION_2_LAYER);

    (*
    The second special effect is very basic, I bet you saw it already in
    some Irrlicht Engine demos: A transparent billboard combined with a
    dynamic light. We simply create a light scene node, let it fly around,
    and to make it look more cool, we attach a billboard scene node to it.
    *)

    // create light

    Node := Smgr.AddLightSceneNode(nil, VECTOR3DF_0, IrrColorF(1.0, 0.6, 0.7, 1.0), 800.0);
    Anim := Smgr.CreateFlyCircleAnimator(Vector3DF(0, 150, 0), 250.0);
    Node.AddAnimator(Anim);
    Anim.Drop;

    // attach billboard to light

    Node := Smgr.AddBillboardSceneNode(Node, Dimension2DF(50, 50));
    Node.SetMaterialFlag(EMF_LIGHTING, False);
    Node.SetMaterialType(EMT_TRANSPARENT_ADD_COLOR);
    Node.SetMaterialTexture(0, Driver.GetTexture('..\..\media\particlewhite.bmp'));

    (*
    The next special effect is a lot more interesting: A particle system.
    The particle system in the Irrlicht Engine is quite modular and
    extensible, but yet easy to use. There is a particle system scene node
    into which you can put a particle emitter, which makes particles come out
    of nothing. These emitters are quite flexible and usually have lots of
    parameters like direction, amount, and color of the particles they
    create.

    There are different emitters, for example a point emitter which lets
    particles pop out at a fixed point. If the particle emitters available
    in the engine are not enough for you, you can easily create your own
    ones, you'll simply have to create a class derived from the
    IParticleEmitter interface and attach it to the particle system using
    setEmitter(). In this example we create a box particle emitter, which
    creates particles randomly inside a box. The parameters define the box,
    direction of the particles, minimal and maximal new particles per
    second, color, and minimal and maximal lifetime of the particles.

    Because only with emitters particle system would be a little bit
    boring, there are particle affectors which modify particles while
    they fly around. Affectors can be added to a particle system for
    simulating additional effects like gravity or wind.
    The particle affector we use in this example is an affector which
    modifies the color of the particles: It lets them fade out. Like the
    particle emitters, additional particle affectors can also be
    implemented by you, simply derive a class from IParticleAffector and
    add it with addAffector().

    After we set a nice material to the particle system, we have a cool
    looking camp fire. By adjusting material, texture, particle emitter,
    and affector parameters, it is also easily possible to create smoke,
    rain, explosions, snow, and so on.
    *)

    // create a particle system

    ps := Smgr.AddParticleSystemSceneNode(False);

    eparam.InitDefault;
    eparam.Direction := Vector3DF(0, 0.06, 0);
    eparam.MinParticlesPerSecond := 80;
    eparam.MaxParticlesPerSecond := 100;
    eparam.MinStartColor := IrrColor(255, 255, 255, 0);
    eparam.MaxStartColor := IrrColor(255, 255, 255, 0);
    eparam.LifeTimeMin := 800;
    eparam.LifeTimeMax := 2000;
    eparam.MinStartSize := Dimension2DF(10, 10);
    eparam.MaxStartSize := Dimension2DF(20, 20);

    em := ps.CreateBoxEmitter(AabBox3DF(-7, 0, -7, 7, 1, 7), eparam);

    ps.SetEmitter(em);  // this grabs the emitter
    em.Drop;            // so we can drop it here without deleting it

    paf := ps.CreateFadeOutParticleAffector;

    ps.AddAffector(paf);  // same goes for the affector
    paf.Drop;

    ps.SetPosition(Vector3DF(-70, 60, 40));
    ps.SetScale(Vector3DF(2, 2, 2));
    ps.SetMaterialFlag(EMF_LIGHTING, False);
    ps.SetMaterialFlag(EMF_ZWRITE_ENABLE, False);
    ps.SetMaterialTexture(0, Driver.GetTexture('..\..\media\fire.bmp'));
    ps.SetMaterialType(EMT_TRANSPARENT_ADD_COLOR);

    (*
    Next we add a volumetric light node, which adds a glowing fake area light to
    the scene. Like with the billboards and particle systems we also assign a
    texture for the desired effect, though this time we'll use a texture animator
    to create the illusion of a magical glowing area effect.
    *)

    n := Smgr.AddVolumeLightSceneNode
         (
           nil,
           -1,
           32,            // Subdivisions on U axis
           32,            // Subdivisions on V axis
           COLOR_0ALPHA,  // foot color
           COLOR_0        // tail color
         );

    if Assigned(n) then
    begin
      n.SetScale(Vector3DF(56, 56, 56));
      n.SetPosition(Vector3DF(-120, 50, 40));

      // load textures for animation
      textures := TInputDataList.Create;
      for g := 7 downto 1 do
      begin
        tmp := '..\..\media\portal' + IntToStr(g) + '.bmp';
        t := Driver.GetTexture(tmp);
        textures.Add(t);
      end;
      glow := Smgr.CreateTextureAnimator(textures, 150);

      textures.Free;

      // add the animator
      n.AddAnimator(glow);

      // drop the animator because it was created with a create() function
      glow.Drop;
    end;

    (*
    As our last special effect, we want a dynamic shadow be casted from an
    animated character. For this we load a DirectX .x model and place it
    into our world. For creating the shadow, we simply need to call
    addShadowVolumeSceneNode(). The color of shadows is only adjustable
    globally for all shadows, by calling ISceneManager::setShadowColor().
    Voila, here is our dynamic shadow.

    Because the character is a little bit too small for this scene, we make
    it bigger using setScale(). And because the character is lighted by a
    dynamic light, we need to normalize the normals to make the lighting on
    it correct. This is always necessary if the scale of a dynamic lighted
    model is not (1,1,1). Otherwise it would get too dark or too bright
    because the normals will be scaled too.
    *)

    // add animated character

    mesh := Smgr.GetMesh('..\..\media\dwarf.x');

    anode := Smgr.AddAnimatedMeshSceneNode(mesh);
    anode.SetPosition(Vector3DF(-50, 20, -60));
    anode.SetAnimationSpeed(15);

    // add shadow
    anode.AddShadowVolumeSceneNode;
    Smgr.SetShadowColor(IrrColor(0, 0, 0, 150));

    // make the model a little bit bigger and normalize its normals
    // because of the scaling, for correct lighting
    anode.SetScale(Vector3DF(2, 2, 2));
    anode.SetMaterialFlag(EMF_NORMALIZE_NORMALS, True);

    (* Finally we simply have to draw everything, that's all. *)

    Camera := Smgr.AddCameraSceneNodeFPS;
    Camera.SetPosition(Vector3DF(-50, 50, -150));
    Camera.SetFarValue(10000.0);   // this increase a shadow visible range.

    // disable mouse cursor
    Device.GetCursorControl.SetVisible(False);

    LastFPS := -1;
    while (Device.Run and Assigned(Driver)) do
    begin
      if Device.IsWindowActive then
      begin
        Driver.BeginScene(True, True, COLOR_0);

        Smgr.DrawAll;

        Driver.EndScene;

        FPS := Driver.GetFPS;

        if (LastFPS <> FPS) then
        begin
          TitleStr := Format('Irrlicht Engine - SpecialFX example [%s] FPS:%d',
            [String(Driver.GetName), FPS]);
          Device.SetWindowCaption(PChar(TitleStr));

          LastFPS := FPS;
        end;
      end;
    end;
    Device.Drop;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
